package view;

/*
 * @(#)AVReceive2.java	1.3 01/03/13
 *
 * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Panel;
import java.net.InetAddress;
import java.util.Vector;

import javax.media.ControllerErrorEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Player;
import javax.media.RealizeCompleteEvent;
import javax.media.control.BufferControl;
import javax.media.protocol.DataSource;
import javax.media.rtp.Participant;
import javax.media.rtp.RTPControl;
import javax.media.rtp.RTPManager;
import javax.media.rtp.ReceiveStream;
import javax.media.rtp.ReceiveStreamListener;
import javax.media.rtp.SessionAddress;
import javax.media.rtp.SessionListener;
import javax.media.rtp.event.ByeEvent;
import javax.media.rtp.event.NewParticipantEvent;
import javax.media.rtp.event.NewReceiveStreamEvent;
import javax.media.rtp.event.ReceiveStreamEvent;
import javax.media.rtp.event.RemotePayloadChangeEvent;
import javax.media.rtp.event.SessionEvent;
import javax.media.rtp.event.StreamMappedEvent;

/**
 * AVReceive2 to receive RTP transmission using the new RTP API.
 */
public class AVReceive2 implements ReceiveStreamListener, SessionListener,
		ControllerListener {
	/**
	 * GUI classes for the Player.
	 */
	class PlayerPanel extends Panel {

		/**
		 * 
		 */
		private static final long serialVersionUID = -7519952154043052062L;
		Component vc, cc;

		PlayerPanel(Player p) {
			setLayout(new BorderLayout());
			if ((vc = p.getVisualComponent()) != null) {
				add("Center", vc);
			}
			if ((cc = p.getControlPanelComponent()) != null) {
				add("South", cc);
			}
		}

		@Override
		public Dimension getPreferredSize() {
			int w = 0, h = 0;
			if (vc != null) {
				Dimension size = vc.getPreferredSize();
				w = size.width;
				h = size.height;
			}
			if (cc != null) {
				Dimension size = cc.getPreferredSize();
				if (w == 0) {
					w = size.width;
				}
				h += size.height;
			}
			if (w < 160) {
				w = 160;
			}
			return new Dimension(w, h);
		}
	}

	/**
	 * GUI classes for the Player.
	 */
	class PlayerWindow extends Frame {

		/**
		 * 
		 */
		private static final long serialVersionUID = -2022454899080501395L;
		Player player;
		ReceiveStream stream;

		PlayerWindow(Player p, ReceiveStream strm) {
			player = p;
			stream = strm;
		}

		@Override
		public void addNotify() {
			super.addNotify();
			pack();
		}

		public void close() {
			player.close();
			setVisible(false);
			dispose();
		}

		public void initialize() {
			add(new PlayerPanel(player));
		}
	}

	/**
	 * A utility class to parse the session addresses.
	 */
	class SessionLabel {

		public String addr = null;
		public int port;
		public int ttl = 1;

		SessionLabel(String session) throws IllegalArgumentException {

			int off;
			String portStr = null, ttlStr = null;

			if (session != null && session.length() > 0) {
				while (session.length() > 1 && session.charAt(0) == '/') {
					session = session.substring(1);
				}

				// Now see if there's a addr specified.
				off = session.indexOf('/');
				if (off == -1) {
					if (!session.equals("")) {
						addr = session;
					}
				} else {
					addr = session.substring(0, off);
					session = session.substring(off + 1);
					// Now see if there's a port specified
					off = session.indexOf('/');
					if (off == -1) {
						if (!session.equals("")) {
							portStr = session;
						}
					} else {
						portStr = session.substring(0, off);
						session = session.substring(off + 1);
						// Now see if there's a ttl specified
						off = session.indexOf('/');
						if (off == -1) {
							if (!session.equals("")) {
								ttlStr = session;
							}
						} else {
							ttlStr = session.substring(0, off);
						}
					}
				}
			}

			if (addr == null) {
				throw new IllegalArgumentException();
			}

			if (portStr != null) {
				try {
					Integer integer = Integer.valueOf(portStr);
					if (integer != null) {
						port = integer.intValue();
					}
				} catch (Throwable t) {
					throw new IllegalArgumentException();
				}
			} else {
				throw new IllegalArgumentException();
			}

			if (ttlStr != null) {
				try {
					Integer integer = Integer.valueOf(ttlStr);
					if (integer != null) {
						ttl = integer.intValue();
					}
				} catch (Throwable t) {
					throw new IllegalArgumentException();
				}
			}
		}
	}

	static void prUsage() {
		System.err.println("Usage: AVReceive2 <session> <session> ...");
		System.err.println("     <session>: <address>/<port>/<ttl>");
		System.exit(0);
	}

	String sessions[] = null;

	RTPManager mgrs[] = null;

	Vector playerWindows = null;

	boolean dataReceived = false;

	Object dataSync = new Object();

	public AVReceive2(String sessions[]) {
		this.sessions = sessions;
	}

	/**
	 * Close the players and the session managers.
	 */
	protected void close() {

		for (int i = 0; i < playerWindows.size(); i++) {
			try {
				((PlayerWindow) playerWindows.elementAt(i)).close();
			} catch (Exception e) {
			}
		}

		playerWindows.removeAllElements();

		// close the RTP session.
		for (int i = 0; i < mgrs.length; i++) {
			if (mgrs[i] != null) {
				mgrs[i].removeTargets("Closing session from AVReceive2");
				mgrs[i].dispose();
				mgrs[i] = null;
			}
		}
	}

	/**
	 * ControllerListener for the Players.
	 */
	@Override
	public synchronized void controllerUpdate(ControllerEvent ce) {

		Player p = (Player) ce.getSourceController();

		if (p == null) {
			return;
		}

		// Get this when the internal players are realized.
		if (ce instanceof RealizeCompleteEvent) {
			PlayerWindow pw = find(p);
			if (pw == null) {
				// Some strange happened.
				System.err.println("Internal error!");
				System.exit(-1);
			}
			pw.initialize();
			pw.setVisible(true);
			p.start();
		}

		if (ce instanceof ControllerErrorEvent) {
			p.removeControllerListener(this);
			PlayerWindow pw = find(p);
			if (pw != null) {
				pw.close();
				playerWindows.removeElement(pw);
			}
			System.err.println("AVReceive2 internal error: " + ce);
		}

	}

	PlayerWindow find(Player p) {
		for (int i = 0; i < playerWindows.size(); i++) {
			PlayerWindow pw = (PlayerWindow) playerWindows.elementAt(i);
			if (pw.player == p) {
				return pw;
			}
		}
		return null;
	}

	PlayerWindow find(ReceiveStream strm) {
		for (int i = 0; i < playerWindows.size(); i++) {
			PlayerWindow pw = (PlayerWindow) playerWindows.elementAt(i);
			if (pw.stream == strm) {
				return pw;
			}
		}
		return null;
	}

	protected boolean initialize() {

		try {
			InetAddress ipAddr;
			SessionAddress localAddr = new SessionAddress();
			SessionAddress destAddr;

			mgrs = new RTPManager[sessions.length];
			playerWindows = new Vector();

			SessionLabel session;

			// Open the RTP sessions.
			for (int i = 0; i < sessions.length; i++) {

				// Parse the session addresses.
				try {
					session = new SessionLabel(sessions[i]);
				} catch (IllegalArgumentException e) {
					System.err
							.println("Failed to parse the session address given: "
									+ sessions[i]);
					return false;
				}

				System.err.println("  - Open RTP session for: addr: "
						+ session.addr + " port: " + session.port + " ttl: "
						+ session.ttl);

				mgrs[i] = RTPManager.newInstance();
				mgrs[i].addSessionListener(this);
				mgrs[i].addReceiveStreamListener(this);

				ipAddr = InetAddress.getByName(session.addr);

				if (ipAddr.isMulticastAddress()) {
					// local and remote address pairs are identical:
					localAddr = new SessionAddress(ipAddr, session.port,
							session.ttl);
					destAddr = new SessionAddress(ipAddr, session.port,
							session.ttl);
				} else {
					localAddr = new SessionAddress(InetAddress.getLocalHost(),
							session.port);
					destAddr = new SessionAddress(ipAddr, session.port);
				}

				mgrs[i].initialize(localAddr);

				// You can try out some other buffer size to see
				// if you can get better smoothness.
				BufferControl bc = (BufferControl) mgrs[i]
						.getControl("javax.media.control.BufferControl");
				if (bc != null) {
					bc.setBufferLength(350);
				}

				mgrs[i].addTarget(destAddr);
			}
		} catch (Exception e) {
			System.err.println("Cannot create the RTP Session: "
					+ e.getMessage());
			return false;
		}

		// Wait for data to arrive before moving on.

		long then = System.currentTimeMillis();
		long waitingPeriod = 30000; // wait for a maximum of 30 secs.

		try {
			synchronized (dataSync) {
				while (!dataReceived
						&& System.currentTimeMillis() - then < waitingPeriod) {
					if (!dataReceived) {
						System.err
								.println("  - Waiting for RTP data to arrive...");
					}
					dataSync.wait(1000);
				}
			}
		} catch (Exception e) {
		}

		if (!dataReceived) {
			System.err.println("No RTP data was received.");
			close();
			return false;
		}

		return true;
	}

	public boolean isDone() {
		return playerWindows.size() == 0;
	}

	/**
	 * ReceiveStreamListener
	 */
	@Override
	public synchronized void update(ReceiveStreamEvent evt) {

		RTPManager mgr = (RTPManager) evt.getSource();
		Participant participant = evt.getParticipant(); // could be null.
		ReceiveStream stream = evt.getReceiveStream(); // could be null.

		if (evt instanceof RemotePayloadChangeEvent) {

			System.err.println("  - Received an RTP PayloadChangeEvent.");
			System.err.println("Sorry, cannot handle payload change.");
			System.exit(0);

		}

		else if (evt instanceof NewReceiveStreamEvent) {

			try {
				stream = ((NewReceiveStreamEvent) evt).getReceiveStream();
				DataSource ds = stream.getDataSource();

				// Find out the formats.
				RTPControl ctl = (RTPControl) ds
						.getControl("javax.media.rtp.RTPControl");
				if (ctl != null) {
					System.err.println("  - Recevied new RTP stream: "
							+ ctl.getFormat());
				} else {
					System.err.println("  - Recevied new RTP stream");
				}

				if (participant == null) {
					System.err
							.println("      The sender of this stream had yet to be identified.");
				} else {
					System.err.println("      The stream comes from: "
							+ participant.getCNAME());
				}

				// create a player by passing datasource to the Media Manager
				Player p = javax.media.Manager.createPlayer(ds);
				if (p == null) {
					return;
				}

				p.addControllerListener(this);
				p.realize();
				PlayerWindow pw = new PlayerWindow(p, stream);
				playerWindows.addElement(pw);

				// Notify intialize() that a new stream had arrived.
				synchronized (dataSync) {
					dataReceived = true;
					dataSync.notifyAll();
				}

			} catch (Exception e) {
				System.err.println("NewReceiveStreamEvent exception "
						+ e.getMessage());
				return;
			}

		}

		else if (evt instanceof StreamMappedEvent) {

			if (stream != null && stream.getDataSource() != null) {
				DataSource ds = stream.getDataSource();
				// Find out the formats.
				RTPControl ctl = (RTPControl) ds
						.getControl("javax.media.rtp.RTPControl");
				System.err.println("  - The previously unidentified stream ");
				if (ctl != null) {
					System.err.println("      " + ctl.getFormat());
				}
				System.err.println("      had now been identified as sent by: "
						+ participant.getCNAME());
			}
		}

		else if (evt instanceof ByeEvent) {

			System.err.println("  - Got \"bye\" from: "
					+ participant.getCNAME());
			PlayerWindow pw = find(stream);
			if (pw != null) {
				pw.close();
				playerWindows.removeElement(pw);
			}
		}

	}

	/*
	 * public static void main(String argv[]) throws UnknownHostException {
	 * 
	 * String ip = InetAddress.getLocalHost().getHostAddress(); argv =new
	 * String[1];
	 * 
	 * ip += "/42050"; argv[0]= ip;
	 * 
	 * if (argv.length == 0) prUsage();
	 * 
	 * AVReceive2 avReceive = new AVReceive2(argv); if (!avReceive.initialize())
	 * { System.err.println("Failed to initialize the sessions.");
	 * System.exit(-1); }
	 * 
	 * // Check to see if AVReceive2 is done. try { while (!avReceive.isDone())
	 * Thread.sleep(1000); } catch (Exception e) {}
	 * 
	 * System.err.println("Exiting AVReceive2"); }
	 */

	/**
	 * SessionListener.
	 */
	@Override
	public synchronized void update(SessionEvent evt) {
		if (evt instanceof NewParticipantEvent) {
			Participant p = ((NewParticipantEvent) evt).getParticipant();
			System.err.println("  - A new participant had just joined: "
					+ p.getCNAME());
		}
	}

}// end of AVReceive2
